package io.gitee.mingbaobaba.security.core.utils;

import io.gitee.mingbaobaba.security.core.annotion.SecurityCheckPermission;
import io.gitee.mingbaobaba.security.core.annotion.SecurityCheckRole;
import io.gitee.mingbaobaba.security.core.constants.ErrorCodeConstant;
import io.gitee.mingbaobaba.security.core.constants.SecurityConstant;
import io.gitee.mingbaobaba.security.core.domain.SecuritySession;

import io.gitee.mingbaobaba.security.core.enums.SecurityConditionType;
import io.gitee.mingbaobaba.security.core.exception.SecurityNoAuthorityException;
import io.gitee.mingbaobaba.security.core.factory.SecurityFactory;
import org.apache.commons.lang3.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.*;
import java.util.function.*;
import java.util.regex.Pattern;

/**
 * <p>公共工具</p>
 *
 * @author yingsheng.ye
 * @version 1.0.0
 * @since 2023/8/28 14:39
 */
public class CommonUtil {

    private CommonUtil() {

    }

    /**
     * 打印banner
     */
    public static void showBanner() {
        if (Boolean.TRUE.equals(SecurityFactory.getConfig.get().getShowBanner())) {
            //打印banner
            String banner = "\n _._|_|_  _ ._  _.___ _  _   ._o_|_   \n" +
                    "(_| |_| |(/_| |(_| _>(/_(_|_|| | |_\\/ \n" +
                    ":: Athena-Security :: 1.4.5        /  \n";
            System.out.println(banner);
        }
    }

    /**
     * 生成token
     */
    public static final Supplier<String> generateToken = () -> {
        String uuid = UUID.randomUUID().toString();
        return Base64.getEncoder().encodeToString(uuid.getBytes(StandardCharsets.UTF_8));
    };

    /**
     * 裁剪token前缀
     */
    public static final UnaryOperator<String> cutPrefixToken = token -> {
        if (StringUtils.isBlank(token)) {
            return null;
        }
        return token.replaceAll(SecurityConstant.AUTHORIZATION_PREFIX, "");
    };


    /**
     * 拼接token前缀
     */
    public static final UnaryOperator<String> appendTokenPrefix = token ->
            MessageFormat.format(SecurityConstant.AUTHORIZATION_TOKEN_KEY, token);


    /**
     * 判断方法或类是否包含注解
     */
    public static final BiPredicate<Method, Class<? extends Annotation>> isAnnotationPresent = (method, annotation) ->
            method.isAnnotationPresent(annotation) || method.getDeclaringClass().isAnnotationPresent(annotation);


    /**
     * 获取方法或类上的注解
     */
    public static final BiFunction<Method, Class<? extends Annotation>, Annotation> getAnnotation = ((method, annotationClass) ->
            null == method.getAnnotation(annotationClass) ?
                    method.getDeclaringClass().getAnnotation(annotationClass) : method.getAnnotation(annotationClass));


    /**
     * 检查权限
     */
    public static final Consumer<Method> checkMethodPermission = method -> {

        //检查登录权限
        SecurityUtil.checkToken();

        //判断是否检查角色
        if (CommonUtil.isAnnotationPresent.test(method, SecurityCheckRole.class)) {
            SecurityCheckRole securityCheckRole = (SecurityCheckRole) CommonUtil.getAnnotation
                    .apply(method, SecurityCheckRole.class);
            if (null != securityCheckRole
                    && !hasMultiPermValid(securityCheckRole.value(), securityCheckRole.conditionType(),
                    CommonUtil.hasRole)) {
                throw new SecurityNoAuthorityException(ErrorCodeConstant.CODE_NO_ROLE_ERR, "无角色权限");
            }
        }

        //判断是否检查权限
        if (CommonUtil.isAnnotationPresent.test(method, SecurityCheckPermission.class)) {
            SecurityCheckPermission securityCheckPermission = (SecurityCheckPermission) CommonUtil.getAnnotation
                    .apply(method, SecurityCheckPermission.class);
            if (null != securityCheckPermission
                    && !hasMultiPermValid(securityCheckPermission.value(), securityCheckPermission.conditionType(),
                    CommonUtil.hasPermission)) {
                throw new SecurityNoAuthorityException(ErrorCodeConstant.CODE_NO_PERMISSION_ERR, "无权限");
            }
        }

    };

    /**
     * 当前用户是否有角色
     */
    public static final Predicate<String> hasRole = s -> {
        SecuritySession securitySession = SecurityUtil.getCurrentSecuritySession();
        List<String> roleList = SecurityFactory.getSecurityPermission.get()
                .getRoleCodeList(securitySession.getLoginId());
        return null != roleList && roleList.stream().anyMatch(item ->
                CommonUtil.strMatch.test(item, s));
    };

    /**
     * 当前用户是否有权限
     */
    public static final Predicate<String> hasPermission = s -> {
        SecuritySession securitySession = SecurityUtil.getCurrentSecuritySession();
        List<String> permissionCodeList = SecurityFactory.getSecurityPermission.get()
                .getPermissionCodeList(securitySession.getLoginId());
        return null != permissionCodeList && permissionCodeList.stream().anyMatch(item ->
                CommonUtil.strMatch.test(item, s));
    };

    /**
     * 拥有角色/权限校验
     *
     * @param arr                 角色码或权限码
     * @param conditionType       条件类型
     * @param conditionalJudgment 判断条件
     * @return true 条件成立 false条件不成立
     */
    public static boolean hasMultiPermValid(String[] arr, SecurityConditionType conditionType,
                                            Predicate<String> conditionalJudgment) {
        boolean noCheckPerm = true;
        if (Objects.nonNull(arr) && arr.length > 0) {
            switch (conditionType) {
                case AND:
                    if (Arrays.stream(arr).allMatch(conditionalJudgment)) {
                        noCheckPerm = false;
                    }
                    break;
                case OR:
                    if (Arrays.stream(arr).anyMatch(conditionalJudgment)) {
                        noCheckPerm = false;
                    }
                    break;
                default:
            }
        }
        return !noCheckPerm;
    }

    /**
     * 两个字符串是否匹配，支持正则表达式
     */
    public static final BiPredicate<String, String> strMatch = (s1, s2) -> {
        if (Objects.isNull(s1) && Objects.isNull(s2)) {
            return true;
        }
        if (Objects.isNull(s1) || Objects.isNull(s2)) {
            return false;
        }
        if (!s2.contains("*")) {
            return s1.equals(s2);
        }
        return Pattern.matches(s2.replace("*", ".*"), s1);
    };


}
